<?php

namespace Hiders\WebmanCrud\Trait;

use Closure;
use Exception;
use Hiders\WebmanCrud\Base\Model;
use Hiders\WebmanCrud\Exception\SaveHookException;
use Illuminate\Contracts\Database\Query\Builder;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Str;
use support\Db;
use support\Request;
use support\Response;
use Throwable;

trait Crud
{
    /**
     * 列表
     * @param Request $request
     * @return Response
     */
    public function index(Request $request): Response
    {
        try {
            if (empty($this->relation) && empty($this->joins)) {
                [$where, $orderBy, $order, $pageSize] = $this->buildSearch();
                $model = $this->model;
            } else {
                [$where, $orderBy, $order, $pageSize] = $this->buildSearch(true);
                $model = $this->model::with($this->relation);
            }
        } catch (Exception $e) {
            return $this->error($e->getMessage());
        }

        if (!empty($this->joins)) {
            foreach ($this->joins as $k => $v) {
                if (is_string($v[0])) {
                    str_contains($v[0], '.') || $v[0] = $this->model->getTable() . '.' . $v[0];
                    $v[2] = $k . '.' . $v[2];
                }
                $model = $model->join($k, ...$v);
            }
            foreach ($this->fields as &$v) {
                str_contains($v, '.') || $v = $this->model->getTable() . '.' . $v;
                str_contains($v, ' AS ') && $v = Db::raw($v);
            }
        }

        $list = $model->where($this->extWhere)->where(function (Builder $query) use ($where) {
            return $query->where($where)->orWhere($this->extOrWhere);
        })->when($this->extOrderBy ?? null, function (Builder $query, $param) {
            return $query->orderByRaw($param);
        })->when($orderBy, function (Builder $query, $param) use ($order) {
            if (is_array($param)) {
                foreach ($param as $v) {
                    $query->orderBy($v, $order);
                }

                return $query;
            } else {
                return $query->orderBy($param, $order);
            }
        })->orderByDesc($this->model->getKeyName())->select($this->fields)->paginate($pageSize);

        if ($this->appendField) {
            foreach ($list as $item) {
                $item->append($this->appendField);
            }
        }

        $this->appendList($list);

        return $this->success('', [
            'list'  => $list->items(),
            'total' => $list->total(),
            ...$this->appendData($where, $request),
        ]);
    }

    /**
     * 构建 selectPage 查询参数
     * @param Request $request
     * @return array
     */
    protected function buildSelectPageSearch(Request $request): array
    {
        return [];
    }

    /**
     * 下拉列表
     * @param Request $request
     * @param string  $filed
     * @return Response
     */
    public function selectPage(Request $request, string $filed = 'name'): Response
    {
        $id = $request->input('id');
        $field = $request->input('field') ?: (in_array($filed, $this->fields) ? $filed : 'title');

        $list = $this->model::query();

        try {
            $where = $this->buildSelectPageSearch($request);
            if (!empty($where)) {
                $list = $list->where($where);
            }
        } catch (Exception $e) {
            return $this->error($e->getMessage());
        }

        if (!empty($id)) {
            $list = $list->where('id', $id);
        }

        if (!empty($this->selectPageOrderBy)) {
            $list = $list->orderBy(...$this->selectPageOrderBy);
        }

        if (in_array('sort', $this->fields)) {
            $list = $list->orderByDesc('sort');
        }

        $list = $list->get(['id', $field . ' AS name']);

        return $this->success('', $list);
    }

    /**
     * 查询参数验证
     * @param array $params
     * @return array
     */
    protected function validateSearch(array $params): array
    {
        return $params;
    }

    /**
     * 构建查询参数
     * @param bool $isRelation 是否关联查询
     * @return array
     */
    protected function buildSearch(bool $isRelation = false): array
    {
        $params = request()->all();
        $search = !empty($params['search']) ? $params['search'] : [];
        $order = !empty($params['order']) ? $params['order'] : 'DESC';
        $orderBy = !empty($params['order_by']) ? $params['order_by'] : $this->model->getKeyName();
        $pageSize = !empty($params['page_size']) ? $params['page_size'] : 10;
        $tablePrefix = $isRelation ? $this->model->getTable() . '.' : '';

        //构建查询
        $where = [];
        foreach ($search as $k => $v) {
            $v = is_string($v) ? trim($v) : $v;
            if ($v === '' || !isset($this->searchFields[$k])) {
                continue;
            } elseif (!isset($this->searchFields[$k][0])) {
                $where[] = [$tablePrefix . $k, $v];
            } elseif ($this->searchFields[$k][0] instanceof Closure) {
                $where[] = $this->searchFields[$k];
            } elseif (is_string($this->searchFields[$k][0])) {
                switch ($this->searchFields[$k][0]) {
                    case '=':
                    case '<':
                    case '<=':
                    case '>':
                    case '>=':
                        $field = $this->searchFields[$k][1] ?? $tablePrefix . $k;
                        $where[] = [$field, $this->searchFields[$k][0], $v];
                        break;
                    case 'like':
                        $field = $this->searchFields[$k][1] ?? $tablePrefix . $k;
                        $where[] = [$field, $this->searchFields[$k][0], "%$v%"];
                        break;
                    case 'in':
                        $field = $this->searchFields[$k][1] ?? $tablePrefix . $k;
                        $where[] = [
                            function (Builder $query) use ($field, $v) {
                                $query->whereIn($field, $v);
                            },
                        ];
                        break;
                    case 'between':
                        if (!empty($v) && count($v) === 2) {
                            $field = $this->searchFields[$k][1] ?? $tablePrefix . $k;
                            $where[] = [
                                function (Builder $query) use ($field, $v) {
                                    $query->whereBetween($field, $v);
                                },
                            ];
                        }
                        break;
                    case 'func':
                    case 'function':
                        $methodName = Str::camel('search_' . ($this->searchFields[$k][1] ?? $k));
                        if (method_exists($this, $methodName)) {
                            $where[] = [$this->$methodName($v, $search)];
                        }
                        break;
                }
            }
        }

        if (is_string($orderBy)) {
            $orderBy = str_contains($orderBy, '.') ? $orderBy : $tablePrefix . $orderBy;
        }

        return [$where, $orderBy, $order, $pageSize];
    }

    protected function appendList(LengthAwarePaginator $list): void
    {
    }

    protected function appendData(array $where, Request $request): array
    {
        return [];
    }

    /**
     * 新增
     * @param Request $request
     * @return Response
     */
    public function add(Request $request): Response
    {
        $params = $request->all();
        $this->buildParams($params);

        $row = $this->model::new();

        try {
            $this->beforeSave($row, $params, 'add');
        } catch (SaveHookException $e) {
            return $this->error('前置存储失败：' . $e->getMessage());
        }

        Db::beginTransaction();
        try {
            $result = $row->fill($params)->save();
            try {
                $this->afterSave($row, $params, 'add');
            } catch (SaveHookException $e) {
                Db::rollBack();

                return $this->error('后置存储失败：' . $e->getMessage());
            }
            Db::commit();
        } catch (Exception $e) {
            Db::rollBack();
            if ($e->getCode() == 23000) {
                return $this->error('数据重复', null, -500);
            }

            return $this->error($e->getMessage(), null, -500);
        }

        if ($result !== false) {
            return $this->success('', $row);
        } else {
            return $this->error('新增失败！');
        }
    }

    /**
     * 获取指定记录信息
     * @param Request $request
     * @return Response
     */
    public function info(Request $request): Response
    {
        $params = $request->all();
        if (empty($params['ids'])) {
            return $this->error('ids参数不能为空');
        }
        $params['ids'] = is_array($params['ids']) ? $params['ids'] : [$params['ids']];

        $row = $this->model::whereIn('id', $params['ids'])->select($this->fields)->get()->toArray();
        if (empty($row)) {
            return $this->error('未找到对应记录');
        }
        if (count($row) === 1) {
            $row = $row[0];
        }

        return $this->success('', $row);
    }

    /**
     * 编辑
     * @param Request $request
     * @return Response
     */
    public function edit(Request $request): Response
    {
        $params = $request->all();
        if (empty($params['ids'])) {
            return $this->error('ids参数不能为空');
        }
        $this->buildParams($params);

        $row = $this->model::find($params['ids'], array_filter($this->fields, function ($value) {
            return !str_contains($value, '.');
        }));
        if (empty($row)) {
            return $this->error('未找到对应记录');
        }
        try {
            $this->beforeSave($row, $params, 'edit');
        } catch (SaveHookException $e) {
            return $this->error('前置存储失败：' . $e->getMessage());
        }

        Db::beginTransaction();
        try {
            $result = $row->fill($params)->save();
            try {
                $this->afterSave($row, $params, 'edit');
            } catch (SaveHookException $e) {
                Db::rollBack();

                return $this->error('后置存储失败：' . $e->getMessage());
            }
            Db::commit();
        } catch (Exception $e) {
            Db::rollBack();

            return $this->error($e->getMessage(), null, -500);
        }

        if ($result !== false) {
            return $this->success('', $row);
        } else {
            return $this->error('修改失败！');
        }
    }

    /**
     * 构建参数
     * @param array $params
     * @return void
     */
    protected function buildParams(array &$params): void
    {
    }

    /**
     * 保存前置
     * @param \Hiders\WebmanCrud\Base\Model $row
     * @param array                         $params
     * @param string                        $method
     * @return void
     * @throws \Hiders\WebmanCrud\Exception\SaveHookException
     */
    protected function beforeSave(Model $row, array $params, string $method): void
    {
        empty($method) && throw new SaveHookException('方法不能为空');
    }

    /**
     * 保存后置
     * @param \Hiders\WebmanCrud\Base\Model $row
     * @param array                         $params
     * @param string                        $method
     * @return void
     * @throws \Hiders\WebmanCrud\Exception\SaveHookException
     */
    protected function afterSave(Model $row, array $params, string $method): void
    {
        empty($method) && throw new SaveHookException('方法不能为空');
    }

    /**
     * 删除
     * @param Request $request
     * @return Response
     */
    public function del(Request $request): Response
    {
        $params = $request->all();
        if (empty($params['ids'])) {
            return $this->error('ids参数不能为空');
        }
        $this->buildParams($params);
        $params['ids'] = is_array($params['ids']) ? $params['ids'] : [$params['ids']];

        try {
            $this->beforeDelete($params);
        } catch (Throwable $e) {
            return $this->error($e->getMessage(), method_exists($e, 'getData') ? $e->getData() : null);
        }

        $pk = $this->model->getKeyName();
        $list = $this->model::whereIn($pk, $params['ids'])->select([$pk])->get();
        $count = 0;

        Db::beginTransaction();
        try {
            foreach ($list as $v) {
                $count += $v->delete();
                $this->afterDelete($v);
            }
        } catch (Throwable $e) {
            Db::rollBack();

            return $this->error($e->getMessage(), null, -500);
        }
        if ($count == count($list)) {
            Db::commit();

            return $this->success();
        } else {
            Db::rollBack();

            return $this->error('删除失败，记录数不一致。');
        }
    }

    /**
     * 删除前置
     * @param array $params
     * @return void
     * @throws \Hiders\WebmanCrud\Exception\SaveHookException
     */
    protected function beforeDelete(array &$params)
    {
        empty($params) && throw new SaveHookException('参数不能为空');
    }

    /**
     * 删除后置
     * @param \Hiders\WebmanCrud\Base\Model $row
     * @return void
     * @throws \Hiders\WebmanCrud\Exception\SaveHookException
     */
    protected function afterDelete(Model $row)
    {
        empty($row) && throw new SaveHookException('未找到对应记录');
    }

    /**
     * 排序
     * @param Request $request
     * @return Response
     */
    public function sort(Request $request): Response
    {
        $params = $request->all();
        foreach ($params as $param) {
            if (empty($param['id']) || empty($param['sort'])) {
                return $this->error('id或sort参数不能为空');
            }
        }

        Db::beginTransaction();
        try {
            foreach ($params as $param) {
                $this->model::where('id', $param['id'])->update(['sort' => $param['sort']]);
            }
            Db::commit();

            return $this->success();
        } catch (Throwable $e) {
            Db::rollBack();

            return $this->error("({$e->getCode()}){$e->getMessage()}", $e->getTrace());
        }
    }

    /**
     * 批量编辑
     * @param Request $request
     * @return Response
     */
    public function multiEdit(Request $request): Response
    {
        $params = $request->all();
        foreach ($params as $param) {
            if (empty($param['id'])) {
                return $this->error('id参数不能为空');
            }
            if (count($param) < 2) {
                return $this->error('参数格式错误');
            }
        }

        Db::beginTransaction();
        try {
            foreach ($params as $param) {
                $this->model::where('id', $param['id'])->update(array_filter($param, fn($key) => in_array($key, [
                    'sort',
                    'status',
                ]), ARRAY_FILTER_USE_KEY));
            }
            Db::commit();

            return $this->success();
        } catch (Throwable $e) {
            Db::rollBack();

            return $this->error("({$e->getCode()}){$e->getMessage()}", $e->getTrace());
        }
    }
}